package com.hmily.dubbo.asm.core;

import org.objectweb.asm.*;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Method;

/**
 * <h1>生成class直接的工具类。</h1>
 *
 * @author hmilyylimh
 * ^_^
 * @version 0.0.1
 * ^_^
 * @date 2021-10-24
 */
public class GenerateClassBytesUtils implements Opcodes {

    private static Class sResultClass = null;

    /**
     * <h2>生成新的类。</h2>
     *
     * @param originClass：样例：UserBean.class
     * @param newClzNameSuffix： 样例：Handler
     * @return
     */
    public static Class toClass(Class originClass, String newClzNameSuffix) throws Exception{
        // sResultClass 仅仅只是针对 originClass 做的缓存类而已，大家做项目的时候记得要做成映射关系即可
        if(sResultClass != null){
            return sResultClass;
        }
        byte[] classBytes = toClassInner(originClass, newClzNameSuffix);

        // 可以想办法将 classBytes 存储为一个文件
        FileOutputStream fileOutputStream = new FileOutputStream(new File(
              "这里填你磁盘的路径",
                originClass.getSimpleName() + newClzNameSuffix + ".class"
        ));
        fileOutputStream.write(classBytes);
        fileOutputStream.close();

        // 还得把 classBytes 加载到 JVM 内存中去
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class<?> loaderClass = Class.forName("java.lang.ClassLoader");
        Method defineClassMethod = loaderClass.getDeclaredMethod("defineClass",
                String.class,
                byte[].class,
                int.class,
                int.class);
        defineClassMethod.setAccessible(true);

        Object respObject = defineClassMethod.invoke(loader, new Object[]{
                originClass.getName() + newClzNameSuffix,
                classBytes,
                0,
                classBytes.length
        });


        // 拿着加载后的 class 返回即可
        return sResultClass = (Class)respObject;
    }

    /**
     * <h2>生成新的类的核心操作。</h2><br/>
     *
     * <li><h2>注意：接下来的重点就是如何用asm来动态产生一个 UserBeanHandler 类。</h2></li>
     *
     * @param originClass：样例：UserBean.class
     * @param newClzNameSuffix： 样例：Handler
     * @return
     */
    private static byte[] toClassInner(Class originClass, String newClzNameSuffix) {
        String newClassSimpleNameAndSuffix = originClass.getSimpleName() + newClzNameSuffix + ".java";

        /**********************************************************************/
        // 利用 ASM 编写创建类文件头的相关信息
        /**********************************************************************/
        ClassWriter classWriter = new ClassWriter(0);
//        // class version 52.0 (52)
//// access flags 0x21
//// signature Ljava/lang/Object;Lcom/hmily/dubbo/asm/core/Handler<Lcom/hmily/dubbo/asm/core/UserBean;>;
//// declaration: com/hmily/dubbo/asm/core/UserBeanHandler implements com.hmily.dubbo.asm.core.Handler<com.hmily.dubbo.asm.core.UserBean>
//        public class com/hmily/dubbo/asm/core/UserBeanHandler implements com/hmily/dubbo/asm/core/Handler {
        classWriter.visit(
                V1_6,
                ACC_PUBLIC + ACC_SUPER,
                // com/hmily/dubbo/asm/core/UserBeanHandler
                Type.getInternalName(originClass) + newClzNameSuffix,
                Type.getDescriptor(Object.class)+Type.getDescriptor(originClass).replace(";","")+"<"+Type.getDescriptor(originClass)+">;",
                Type.getDescriptor(Object.class),
                new String[]{ Type.getInternalName(Handler.class) }
        );
        // UserBeanHandler.java
        classWriter.visitSource(newClassSimpleNameAndSuffix, null);

        /**********************************************************************/
        // 创建构造方法
        /**********************************************************************/
        {
//
//            // compiled from: UserBeanHandler.java
//
//            // access flags 0x1
//            public <init>()V
            MethodVisitor methodVisitor = classWriter.visitMethod(
                    ACC_PUBLIC,
                    "<init>",
                    "()V",
                    null,
                    null
            );
            methodVisitor.visitCode();
//                    L0
//            LINENUMBER 12 L0
//            ALOAD 0
            methodVisitor.visitVarInsn(ALOAD, 0);
//            INVOKESPECIAL java/lang/Object.<init> ()V
            methodVisitor.visitMethodInsn(INVOKESPECIAL,
                    Type.getInternalName(Object.class),
                    "<init>",
                    "()V"
                    );
//                    RETURN
            methodVisitor.visitInsn(RETURN);
//            L1
            Label L1 = new Label();
            methodVisitor.visitLabel(L1);
//            LOCALVARIABLE this Lcom/hmily/dubbo/asm/core/UserBeanHandler; L0 L1 0
//            MAXSTACK = 1
//            MAXLOCALS = 1
            methodVisitor.visitMaxs(1, 1);
//
            methodVisitor.visitEnd();
        }

        /**********************************************************************/
        // 创建 addBefore 方法
        /**********************************************************************/
        {
//            // access flags 0x1
//            public addBefore(Lcom/hmily/dubbo/asm/core/UserBean;)V
            MethodVisitor methodVisitor = classWriter.visitMethod(
                    ACC_PUBLIC,
                    "addBefore",
                    "(" + Type.getDescriptor(originClass) + ")V",
                    null,
                    null
            );
            methodVisitor.visitCode();

//            L0
//            LINENUMBER 16 L0
//            ALOAD 1
            methodVisitor.visitVarInsn(ALOAD, 1);
//            INVOKEVIRTUAL com/hmily/dubbo/asm/core/UserBean.getName ()Ljava/lang/String;
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(originClass),
                    "getName",
                    "()" + Type.getDescriptor(String.class));
//            IFNULL L1
            Label L1 = new Label();
            methodVisitor.visitJumpInsn(IFNULL, L1);
//            ALOAD 1
            methodVisitor.visitVarInsn(ALOAD, 1);
//            INVOKEVIRTUAL com/hmily/dubbo/asm/core/UserBean.getName ()Ljava/lang/String;
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(originClass),
                    "getName",
                    "()" + Type.getDescriptor(String.class));
//            INVOKEVIRTUAL java/lang/String.length ()I
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(String.class),
                    "length",
                    "()I");
//            IFLE L1
            methodVisitor.visitJumpInsn(IFLE, L1);
//            L2
//            LINENUMBER 19 L2

            /**********************************************************************/
            // 接下来要干的事情就是：u.setName("#BEFORE#" + u.getName());
            /**********************************************************************/
//            ALOAD 1
            methodVisitor.visitVarInsn(ALOAD, 1);
//            NEW java/lang/StringBuilder
            methodVisitor.visitTypeInsn(NEW, Type.getInternalName(StringBuilder.class));
//            DUP
            methodVisitor.visitInsn(DUP);

//            INVOKESPECIAL java/lang/StringBuilder.<init> ()V
            methodVisitor.visitMethodInsn(INVOKESPECIAL,
                    Type.getInternalName(StringBuilder.class),
                    "<init>",
                    "()V");
//            LDC "#BEFORE#"
            methodVisitor.visitLdcInsn("#BEFORE#");
//            INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(StringBuilder.class),
                    "append",
                    "("+ Type.getDescriptor(String.class) + ")" + Type.getDescriptor(StringBuilder.class));
//            ALOAD 1
            methodVisitor.visitVarInsn(ALOAD, 1);
//            INVOKEVIRTUAL com/hmily/dubbo/asm/core/UserBean.getName ()Ljava/lang/String;
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(originClass),
                    "getName",
                    "()" + Type.getDescriptor(String.class));
//            INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(StringBuilder.class),
                    "append",
                    "("+ Type.getDescriptor(String.class) + ")" + Type.getDescriptor(StringBuilder.class));
//            INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(StringBuilder.class),
                    "toString",
                    "()" + Type.getDescriptor(String.class));
//            INVOKEVIRTUAL com/hmily/dubbo/asm/core/UserBean.setName (Ljava/lang/String;)V
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(originClass),
                    "setName",
                    "(" + Type.getDescriptor(String.class)+")V");
//                    L1
            methodVisitor.visitLabel(L1);
//            LINENUMBER 21 L1
//            FRAME SAME
            methodVisitor.visitFrame(F_SAME, 0, null, 0, null);
//            RETURN
            methodVisitor.visitInsn(RETURN);
//                    L3
//            LOCALVARIABLE this Lcom/hmily/dubbo/asm/core/UserBeanHandler; L0 L3 0
//            LOCALVARIABLE u Lcom/hmily/dubbo/asm/core/UserBean; L0 L3 1
//            MAXSTACK = 3
//            MAXLOCALS = 2
            methodVisitor.visitMaxs(3, 2);
//
            methodVisitor.visitEnd();
        }

        /**********************************************************************/
        // 创建桥接 addBefore 方法
        /**********************************************************************/
        {
//            // access flags 0x1041
//            public synthetic bridge addBefore(Ljava/lang/Object;)V
            MethodVisitor methodVisitor = classWriter.visitMethod(ACC_PUBLIC + ACC_SYNTHETIC + ACC_BRIDGE,
                    "addBefore",
                    "(" + Type.getDescriptor(Object.class) + ")V",
                    null,
                    null
            );
            methodVisitor.visitCode();
//                    L0
//            LINENUMBER 12 L0
//            ALOAD 0
            methodVisitor.visitVarInsn(ALOAD, 0);
//            ALOAD 1
            methodVisitor.visitVarInsn(ALOAD, 1);
//            CHECKCAST com/hmily/dubbo/asm/core/UserBean
            methodVisitor.visitTypeInsn(CHECKCAST, Type.getInternalName(originClass));
//            INVOKEVIRTUAL com/hmily/dubbo/asm/core/UserBeanHandler.addBefore (Lcom/hmily/dubbo/asm/core/UserBean;)V
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL,
                    Type.getInternalName(originClass) + newClzNameSuffix,
                    "addBefore",
                    "(" + Type.getDescriptor(originClass) + ")V");
//                    RETURN
            methodVisitor.visitInsn(RETURN);
//            L1
            Label L1 = new Label();
            methodVisitor.visitLabel(L1);
//            LOCALVARIABLE this Lcom/hmily/dubbo/asm/core/UserBeanHandler; L0 L1 0
//            MAXSTACK = 2
//            MAXLOCALS = 2
            methodVisitor.visitMaxs(2, 2);
//        }

            methodVisitor.visitEnd();
        }



        classWriter.visitEnd();
        return classWriter.toByteArray();
    }
}
